{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "whjsJasuhstV"
   },
   "source": [
    "<a href=\"https://colab.research.google.com/github/jeffheaton/app_generative_ai/blob/main/t81_559_class_03_5_book.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "euOZxlIMhstX"
   },
   "source": [
    "# T81-559: Applications of Generative Artificial Intelligence\n",
    "**Module 3: Large Language Models**\n",
    "* Instructor: [Jeff Heaton](https://sites.wustl.edu/jeffheaton/), McKelvey School of Engineering, [Washington University in St. Louis](https://engineering.wustl.edu/Programs/Pages/default.aspx)\n",
    "* For more information visit the [class website](https://sites.wustl.edu/jeffheaton/t81-558/)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "d4Yov72PhstY"
   },
   "source": [
    "# Module 3 Material\n",
    "\n",
    "* Part 3.1: Foundation Models [[Video]](https://www.youtube.com/watch?v=Gb0tk5qq1fA) [[Notebook]](t81_559_class_03_1_llm.ipynb)\n",
    "* Part 3.2: Text Generation [[Video]](https://www.youtube.com/watch?v=lB97Lqt7q58) [[Notebook]](t81_559_class_03_2_text_gen.ipynb)\n",
    "* Part 3.3: Text Summarization [[Video]](https://www.youtube.com/watch?v=3MoIUXE2eEU) [[Notebook]](t81_559_class_03_3_text_summary.ipynb)\n",
    "* Part 3.4: Text Classification [[Video]](https://www.youtube.com/watch?v=2VpOwFIGmA8) [[Notebook]](t81_559_class_03_4_classification.ipynb)\n",
    "* **Part 3.5: LLM Writes a Book** [[Video]](https://www.youtube.com/watch?v=iU40Rttlb_Q) [[Notebook]](t81_559_class_03_5_book.ipynb)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "AcAUP0c3hstY"
   },
   "source": [
    "# Google CoLab Instructions\n",
    "\n",
    "The following code ensures that Google CoLab is running and maps Google Drive if needed."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "xsI496h5hstZ",
    "outputId": "3dc269f8-d90a-4f51-cc22-7affe4557fab"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Note: using Google CoLab\n",
      "Requirement already satisfied: langchain in /usr/local/lib/python3.10/dist-packages (0.2.0)\n",
      "Requirement already satisfied: langchain_openai in /usr/local/lib/python3.10/dist-packages (0.1.7)\n",
      "Requirement already satisfied: langchain_community in /usr/local/lib/python3.10/dist-packages (0.2.0)\n",
      "Requirement already satisfied: pypdf in /usr/local/lib/python3.10/dist-packages (4.2.0)\n",
      "Requirement already satisfied: pdfkit in /usr/local/lib/python3.10/dist-packages (1.0.0)\n",
      "Requirement already satisfied: PyYAML>=5.3 in /usr/local/lib/python3.10/dist-packages (from langchain) (6.0.1)\n",
      "Requirement already satisfied: SQLAlchemy<3,>=1.4 in /usr/local/lib/python3.10/dist-packages (from langchain) (2.0.30)\n",
      "Requirement already satisfied: aiohttp<4.0.0,>=3.8.3 in /usr/local/lib/python3.10/dist-packages (from langchain) (3.9.5)\n",
      "Requirement already satisfied: async-timeout<5.0.0,>=4.0.0 in /usr/local/lib/python3.10/dist-packages (from langchain) (4.0.3)\n",
      "Requirement already satisfied: dataclasses-json<0.7,>=0.5.7 in /usr/local/lib/python3.10/dist-packages (from langchain) (0.6.6)\n",
      "Requirement already satisfied: langchain-core<0.3.0,>=0.2.0 in /usr/local/lib/python3.10/dist-packages (from langchain) (0.2.0)\n",
      "Requirement already satisfied: langchain-text-splitters<0.3.0,>=0.2.0 in /usr/local/lib/python3.10/dist-packages (from langchain) (0.2.0)\n",
      "Requirement already satisfied: langsmith<0.2.0,>=0.1.17 in /usr/local/lib/python3.10/dist-packages (from langchain) (0.1.59)\n",
      "Requirement already satisfied: numpy<2,>=1 in /usr/local/lib/python3.10/dist-packages (from langchain) (1.25.2)\n",
      "Requirement already satisfied: pydantic<3,>=1 in /usr/local/lib/python3.10/dist-packages (from langchain) (2.7.1)\n",
      "Requirement already satisfied: requests<3,>=2 in /usr/local/lib/python3.10/dist-packages (from langchain) (2.31.0)\n",
      "Requirement already satisfied: tenacity<9.0.0,>=8.1.0 in /usr/local/lib/python3.10/dist-packages (from langchain) (8.3.0)\n",
      "Requirement already satisfied: openai<2.0.0,>=1.24.0 in /usr/local/lib/python3.10/dist-packages (from langchain_openai) (1.30.1)\n",
      "Requirement already satisfied: tiktoken<1,>=0.7 in /usr/local/lib/python3.10/dist-packages (from langchain_openai) (0.7.0)\n",
      "Requirement already satisfied: typing_extensions>=4.0 in /usr/local/lib/python3.10/dist-packages (from pypdf) (4.11.0)\n",
      "Requirement already satisfied: aiosignal>=1.1.2 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (1.3.1)\n",
      "Requirement already satisfied: attrs>=17.3.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (23.2.0)\n",
      "Requirement already satisfied: frozenlist>=1.1.1 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (1.4.1)\n",
      "Requirement already satisfied: multidict<7.0,>=4.5 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (6.0.5)\n",
      "Requirement already satisfied: yarl<2.0,>=1.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (1.9.4)\n",
      "Requirement already satisfied: marshmallow<4.0.0,>=3.18.0 in /usr/local/lib/python3.10/dist-packages (from dataclasses-json<0.7,>=0.5.7->langchain) (3.21.2)\n",
      "Requirement already satisfied: typing-inspect<1,>=0.4.0 in /usr/local/lib/python3.10/dist-packages (from dataclasses-json<0.7,>=0.5.7->langchain) (0.9.0)\n",
      "Requirement already satisfied: jsonpatch<2.0,>=1.33 in /usr/local/lib/python3.10/dist-packages (from langchain-core<0.3.0,>=0.2.0->langchain) (1.33)\n",
      "Requirement already satisfied: packaging<24.0,>=23.2 in /usr/local/lib/python3.10/dist-packages (from langchain-core<0.3.0,>=0.2.0->langchain) (23.2)\n",
      "Requirement already satisfied: orjson<4.0.0,>=3.9.14 in /usr/local/lib/python3.10/dist-packages (from langsmith<0.2.0,>=0.1.17->langchain) (3.10.3)\n",
      "Requirement already satisfied: anyio<5,>=3.5.0 in /usr/local/lib/python3.10/dist-packages (from openai<2.0.0,>=1.24.0->langchain_openai) (3.7.1)\n",
      "Requirement already satisfied: distro<2,>=1.7.0 in /usr/lib/python3/dist-packages (from openai<2.0.0,>=1.24.0->langchain_openai) (1.7.0)\n",
      "Requirement already satisfied: httpx<1,>=0.23.0 in /usr/local/lib/python3.10/dist-packages (from openai<2.0.0,>=1.24.0->langchain_openai) (0.27.0)\n",
      "Requirement already satisfied: sniffio in /usr/local/lib/python3.10/dist-packages (from openai<2.0.0,>=1.24.0->langchain_openai) (1.3.1)\n",
      "Requirement already satisfied: tqdm>4 in /usr/local/lib/python3.10/dist-packages (from openai<2.0.0,>=1.24.0->langchain_openai) (4.66.4)\n",
      "Requirement already satisfied: annotated-types>=0.4.0 in /usr/local/lib/python3.10/dist-packages (from pydantic<3,>=1->langchain) (0.6.0)\n",
      "Requirement already satisfied: pydantic-core==2.18.2 in /usr/local/lib/python3.10/dist-packages (from pydantic<3,>=1->langchain) (2.18.2)\n",
      "Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/dist-packages (from requests<3,>=2->langchain) (3.3.2)\n",
      "Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.10/dist-packages (from requests<3,>=2->langchain) (3.7)\n",
      "Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.10/dist-packages (from requests<3,>=2->langchain) (2.0.7)\n",
      "Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.10/dist-packages (from requests<3,>=2->langchain) (2024.2.2)\n",
      "Requirement already satisfied: greenlet!=0.4.17 in /usr/local/lib/python3.10/dist-packages (from SQLAlchemy<3,>=1.4->langchain) (3.0.3)\n",
      "Requirement already satisfied: regex>=2022.1.18 in /usr/local/lib/python3.10/dist-packages (from tiktoken<1,>=0.7->langchain_openai) (2023.12.25)\n",
      "Requirement already satisfied: exceptiongroup in /usr/local/lib/python3.10/dist-packages (from anyio<5,>=3.5.0->openai<2.0.0,>=1.24.0->langchain_openai) (1.2.1)\n",
      "Requirement already satisfied: httpcore==1.* in /usr/local/lib/python3.10/dist-packages (from httpx<1,>=0.23.0->openai<2.0.0,>=1.24.0->langchain_openai) (1.0.5)\n",
      "Requirement already satisfied: h11<0.15,>=0.13 in /usr/local/lib/python3.10/dist-packages (from httpcore==1.*->httpx<1,>=0.23.0->openai<2.0.0,>=1.24.0->langchain_openai) (0.14.0)\n",
      "Requirement already satisfied: jsonpointer>=1.9 in /usr/local/lib/python3.10/dist-packages (from jsonpatch<2.0,>=1.33->langchain-core<0.3.0,>=0.2.0->langchain) (2.4)\n",
      "Requirement already satisfied: mypy-extensions>=0.3.0 in /usr/local/lib/python3.10/dist-packages (from typing-inspect<1,>=0.4.0->dataclasses-json<0.7,>=0.5.7->langchain) (1.0.0)\n",
      "Reading package lists... Done\n",
      "Building dependency tree... Done\n",
      "Reading state information... Done\n",
      "wkhtmltopdf is already the newest version (0.12.6-2).\n",
      "0 upgraded, 0 newly installed, 0 to remove and 44 not upgraded.\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "\n",
    "try:\n",
    "    from google.colab import drive, userdata\n",
    "    COLAB = True\n",
    "    print(\"Note: using Google CoLab\")\n",
    "except:\n",
    "    print(\"Note: not using Google CoLab\")\n",
    "    COLAB = False\n",
    "\n",
    "# OpenAI Secrets\n",
    "if COLAB:\n",
    "    os.environ[\"OPENAI_API_KEY\"] = userdata.get('OPENAI_API_KEY')\n",
    "\n",
    "# Install needed libraries in CoLab\n",
    "if COLAB:\n",
    "    !pip install langchain langchain_openai langchain_community pypdf pdfkit\n",
    "    !apt-get install wkhtmltopdf"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "pC9A-LaYhsta"
   },
   "source": [
    "# 3.5: LLM Writes a Book\n",
    "\n",
    "This section will demonstrate how to effectively utilize the LLM to write a book. Writing a book is a uniquely complex challenge that requires planning and execution. Unlike simpler tasks, drafting a full-length book is a journey that extends beyond a single session with an LLM due to its extensive length and depth. However, with a systematic approach, you can harness the capabilities of an LLM to create a comprehensive manuscript, empowering you to conquer this challenge.\n",
    "\n",
    "This section presents a method to craft a book using an LLM, which involves breaking down the book creation process into manageable segments. Our journey begins with a foundational step: selecting a subject for the book. Once a subject is chosen, the process unfolds through a series of iterative interactions with the LLM, each building upon the last to gradually construct the complete book, igniting your passion for writing.\n",
    "\n",
    "Initially, we prompt the LLM to generate a random title based on the given subject. This title sets the thematic tone for the book and guides the subsequent steps. Following the title, we request the LLM to create a random synopsis. This synopsis acts as a narrative backbone, offering a brief overview of the book’s content and direction.\n",
    "\n",
    "With a synopsis, the next step is to generate a table of contents. This table outlines the main chapters and sections of the book, providing a structured framework that organizes the content logically and cohesively. It is from this framework that the book starts to take shape.\n",
    "The next phase involves creating each chapter. We engage in a focused session with the LLM for every chapter outlined in the table of contents. During each session, we provide the LLM with the synopsis and the complete table of contents to ensure consistency and continuity throughout the book. This iterative process allows each chapter to be crafted with attention to detail, ensuring it aligns with the previously established narrative and thematic elements.\n",
    "\n",
    "By methodically iterating through these steps, we harness the capabilities of an LLM to transform a simple idea into a detailed and well-structured book. This chapter will guide you through each stage, providing practical advice on collaborating with an LLM to achieve more complex tasks.\n",
    "\n",
    "We begin by accessing a large language model with a temperature of 0.7. We use a higher temperature to encourage creativity."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "id": "dfISNTpwOKiZ"
   },
   "outputs": [],
   "source": [
    "from langchain.chains.summarize import load_summarize_chain\n",
    "from langchain import OpenAI, PromptTemplate\n",
    "from langchain_openai import ChatOpenAI\n",
    "from IPython.display import display_markdown\n",
    "\n",
    "MODEL = 'gpt-4o-mini'\n",
    "\n",
    "llm = ChatOpenAI(\n",
    "        model=MODEL,\n",
    "        temperature=0.7,\n",
    "        n=1\n",
    "    )\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "OLUWbW7NRp4m"
   },
   "source": [
    "We create a simple utility function to query the LLM with a custom system prompt. The system prompt tells the LLM that it is assisting in creating a book."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "id": "v8vGrZcjTvwZ"
   },
   "outputs": [],
   "source": [
    "from langchain_core.messages import HumanMessage, SystemMessage\n",
    "from langchain_core.prompts.chat import (\n",
    "    ChatPromptTemplate,\n",
    "    HumanMessagePromptTemplate,\n",
    "    SystemMessagePromptTemplate,\n",
    ")\n",
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "def query_llm(prompt):\n",
    "  messages = [\n",
    "      SystemMessage(\n",
    "          content=\"You are assistant helping to write a book.\"\n",
    "      ),\n",
    "      HumanMessage(\n",
    "          content=prompt\n",
    "      ),\n",
    "  ]\n",
    "\n",
    "  output = llm.invoke(messages)\n",
    "  return output.content\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "FOWb0XT7RlYg"
   },
   "source": [
    "## Generate Title, Synopsis and Table of Contents\n",
    "\n",
    "For this book, we allow the user to specify the subject specified by the SUBJECT variable. We then request the LLM to generate a random title based on this subject. It is essential that the prompt request that the LLM only the; LLMs often like to prefix with text such as \"Here is a random title.\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "YpEgTYV1TvpH",
    "outputId": "d5f348e7-6bbb-458e-f60d-f6c8eddba0e3"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The Shadow Protocol\n"
     ]
    }
   ],
   "source": [
    "SUBJECT = \"international spy thriller\"\n",
    "\n",
    "title = query_llm(f\"\"\"\n",
    "Give me a random title for a book on the subject '{SUBJECT}'.\n",
    "Return only the title, no additional text.\n",
    "\"\"\").strip(\" '\\\"\")\n",
    "print(title)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "fXyOF_oYtMlR"
   },
   "source": [
    "Now that we have a title, we can request a random synopsis of the book."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "KxYdLzYCTvgQ",
    "outputId": "99bcaade-468b-4188-d808-a4cbd6b74739"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "In \"International Spy Thriller,\" follow the gripping tale of a seasoned spy who must navigate a web of deception, betrayal, and danger as they race against time to prevent a global catastrophe. From the bustling streets of London to the remote mountains of the Himalayas, the protagonist must utilize their wit, skills, and resourcefulness to outsmart their enemies and uncover a sinister plot that threatens world peace. With high-stakes action, unexpected twists, and heart-pounding suspense, this novel will keep readers on the edge of their seats until the final, explosive showdown.\n"
     ]
    }
   ],
   "source": [
    "synopsis = query_llm(f\"\"\"\n",
    "Give me a synopsis for a book of the title '{SUBJECT}' for a book on the subject '{SUBJECT}'.\n",
    "Return only the synopsis, no additional text.\n",
    "\"\"\").strip(\" '\\\"\")\n",
    "print(synopsis)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "U6ju-lJEttjw"
   },
   "source": [
    "Next, we generate the table of contents. For this generation, we provide all previous information. We also request a particular format for the table of contents. You may notice that I ask for the chapter numbers, even though they are an increasing numeric count and could easily be derived. This process works better because the LLM wants to provide the chapter numbers, and attempts to suppress chapter numbers are often inconsistent. It is easier to allow the LLM to generate chapter numbers but control where it generates them so that I can consistently remove them later."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "9IhXP9PJTvXq",
    "outputId": "813f24ee-1379-4cc0-b26e-69fee44c5122"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1|The Assignment Unveiled\n",
      "2|London Undercover\n",
      "3|Trail of Deception\n",
      "4|Himalayan Hideout\n",
      "5|Race Against Time\n",
      "6|Unraveling the Conspiracy\n",
      "7|Enemies Within\n",
      "8|Final Showdown\n"
     ]
    }
   ],
   "source": [
    "toc = query_llm(f\"\"\"\n",
    "Give me a table of contents for a book of the title '{title}' for a book on\n",
    "the subject '{SUBJECT}' the book synopsis is '{synopsis}'.\n",
    "Return the table of contents as a list of chapter titles.\n",
    "Separate the chapter number and chapter title with a pipe character '|'.\n",
    "Return only the chapter names, no additional text.\n",
    "\"\"\").strip(\" '\\\"\")\n",
    "print(toc)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "Mvh6hlVBCN2f"
   },
   "source": [
    "We must now parse the table of contents and remove the pipes and chapter numbers."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "k-A4RgyR3Mid",
    "outputId": "e7d15614-454f-4bd8-ed2f-ac122253f0ed"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['The Assignment Unveiled', 'London Undercover', 'Trail of Deception', 'Himalayan Hideout', 'Race Against Time', 'Unraveling the Conspiracy', 'Enemies Within', 'Final Showdown']\n"
     ]
    }
   ],
   "source": [
    "# Split the string into lines\n",
    "lines = toc.splitlines()\n",
    "\n",
    "# Extract titles using list comprehension\n",
    "toc2 = [line.split('|')[1].strip() for line in lines if line]\n",
    "\n",
    "# Print the list of titles\n",
    "print(toc2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "vMXLYGi_RxNp"
   },
   "source": [
    "## Generate the Chapters of the Book\n",
    "\n",
    "Next, we create a function capable of producing the text that makes up a chapter. To ensure that the function has enough context to generate each chapter, we provide the synopsis, the table of contents, and the chapter number. To test this code, we request that it develop a single chapter."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "ATXhVI9NfUTQ",
    "outputId": "db5a8fc5-364e-40c9-98c8-037064f57f7d"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The dossier lay open on the desk, its contents shrouded in secrecy. A single photograph stared back at the seasoned spy, its subjects unknown but their significance undeniable. With a sense of urgency hanging in the air, the mission ahead began to take shape. This was no ordinary assignment; it was the kind that could change the course of history. As the spy delved deeper into the details, a chilling realization set in - the world was on the brink of a catastrophe, and only they stood between chaos and order. With a steely resolve, the spy accepted the challenge, knowing that the path ahead was fraught with deception, betrayal, and danger. The shadows beckoned, and the stage was set for a high-stakes game where every move could be their last.\n"
     ]
    }
   ],
   "source": [
    "def render_chapter(num, chapter_title, title, subject, synopsis, toc):\n",
    "  txt = query_llm(f\"\"\"\n",
    "  Write Chapter {num}, titled \"{chapter_title}\" for a book of the title '{title}' for a book on\n",
    "  the subject '{subject}' the book synopsis is '{synopsis}' the table of contents is '{toc}'.\n",
    "  Give me only the chapter text, no chapter heading, no chapter title, number, no additional text.\n",
    "  \"\"\").strip(\" '\\\"\")\n",
    "  return txt\n",
    "\n",
    "txt = render_chapter(1, toc2[0], title, SUBJECT, synopsis, toc)\n",
    "print(txt)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "6s7Igm5cSf50"
   },
   "source": [
    "We can now generate the entire book in Markdown, which allows some formatting. We begin by rendering the title and synopsis, the table of contents, and each chapter."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "uq8xv3RIBkIv",
    "outputId": "e325aace-e375-469d-895d-bee3129adef6"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Rendering chapter 1/8: The Assignment Unveiled\n",
      "Rendering chapter 2/8: London Undercover\n",
      "Rendering chapter 3/8: Trail of Deception\n",
      "Rendering chapter 4/8: Himalayan Hideout\n",
      "Rendering chapter 5/8: Race Against Time\n",
      "Rendering chapter 6/8: Unraveling the Conspiracy\n",
      "Rendering chapter 7/8: Enemies Within\n",
      "Rendering chapter 8/8: Final Showdown\n"
     ]
    }
   ],
   "source": [
    "book = \"\"\n",
    "\n",
    "# Render the title and synopsis\n",
    "book += f\"# {title}\\n\"\n",
    "book += f\"{synopsis}\\n\"\n",
    "\n",
    "# Render the toc\n",
    "book += f\"\\n## Table of Contents\\n\\n\"\n",
    "num = 1\n",
    "for chapter_title in toc2:\n",
    "  book += f\"{num}. {chapter_title}\\n\"\n",
    "  num += 1\n",
    "\n",
    "# Render the book\n",
    "chapter = 1\n",
    "for chapter_title in toc2:\n",
    "  print(f\"Rendering chapter {chapter}/{len(toc2)}: {chapter_title}\")\n",
    "  txt = render_chapter(chapter, chapter_title, title, SUBJECT, synopsis, toc)\n",
    "  book += f\"\\n\\n## Chapter {chapter}: {chapter_title}\\n\"\n",
    "  book += f\"{txt}\\n\"\n",
    "  chapter += 1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "xXVa0IxKR5V7"
   },
   "source": [
    "## Generate a PDF of the Book\n",
    "\n",
    "Now that we have generated the book, we have saved it as a PDF."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "DJNvL2xsfVoF",
    "outputId": "44262bfa-88af-4e7b-f536-47d68bd48cfe"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import markdown\n",
    "import pdfkit\n",
    "\n",
    "# Convert Markdown to HTML\n",
    "html = markdown.markdown(book)\n",
    "options = {\n",
    "    'encoding': \"UTF-8\",  # Ensures UTF-8 encoding\n",
    "}\n",
    "\n",
    "pdfkit.from_string(html, 'output.pdf', options=options)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "NTsMT8sYYh6C"
   },
   "source": [
    "We can now download the generated book."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 17
    },
    "id": "pqcpXCsABZZo",
    "outputId": "14814401-c301-411f-b906-e6cf481b99be"
   },
   "outputs": [
    {
     "data": {
      "application/javascript": [
       "\n",
       "    async function download(id, filename, size) {\n",
       "      if (!google.colab.kernel.accessAllowed) {\n",
       "        return;\n",
       "      }\n",
       "      const div = document.createElement('div');\n",
       "      const label = document.createElement('label');\n",
       "      label.textContent = `Downloading \"${filename}\": `;\n",
       "      div.appendChild(label);\n",
       "      const progress = document.createElement('progress');\n",
       "      progress.max = size;\n",
       "      div.appendChild(progress);\n",
       "      document.body.appendChild(div);\n",
       "\n",
       "      const buffers = [];\n",
       "      let downloaded = 0;\n",
       "\n",
       "      const channel = await google.colab.kernel.comms.open(id);\n",
       "      // Send a message to notify the kernel that we're ready.\n",
       "      channel.send({})\n",
       "\n",
       "      for await (const message of channel.messages) {\n",
       "        // Send a message to notify the kernel that we're ready.\n",
       "        channel.send({})\n",
       "        if (message.buffers) {\n",
       "          for (const buffer of message.buffers) {\n",
       "            buffers.push(buffer);\n",
       "            downloaded += buffer.byteLength;\n",
       "            progress.value = downloaded;\n",
       "          }\n",
       "        }\n",
       "      }\n",
       "      const blob = new Blob(buffers, {type: 'application/binary'});\n",
       "      const a = document.createElement('a');\n",
       "      a.href = window.URL.createObjectURL(blob);\n",
       "      a.download = filename;\n",
       "      div.appendChild(a);\n",
       "      a.click();\n",
       "      div.remove();\n",
       "    }\n",
       "  "
      ],
      "text/plain": [
       "<IPython.core.display.Javascript object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/javascript": [
       "download(\"download_f9b78315-fc30-4709-93d9-fdeadebec978\", \"output.pdf\", 32042)"
      ],
      "text/plain": [
       "<IPython.core.display.Javascript object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from google.colab import files\n",
    "files.download(\"output.pdf\")"
   ]
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "colab": {
   "provenance": []
  },
  "kernelspec": {
   "display_name": "Python 3.11 (genai)",
   "language": "python",
   "name": "genai"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.8"
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
